1 /**
2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 This module can deduce the system compiler flags, if possible, from the
11 compiler specified in a CompileCommand.
12 
13 The module assumes that during an execution the system flags for a compiler do
14 not change thus they can be cached. This avoids having to invoke the compiler
15 more than necessary.
16 
17 This module exists for those times that:
18  * a cross-compiler which uses other system headers than the hosts system
19    compiler. E.g. clang-tidy do not know *what* these are thus this module
20    discoveres them and provide them.
21  * multiple compiler versions are used in a build and each have different
22    headers.
23 */
24 module compile_db.system_compiler;
25 
26 import logger = std.experimental.logger;
27 import std.algorithm : countUntil, map;
28 import std.array : empty, array;
29 import std..string : startsWith, stripLeft, splitLines;
30 
31 import compile_db : CompileCommand, shouldEqual, shouldBeIn;
32 
33 @safe:
34 
35 struct Compiler {
36     string value;
37     alias value this;
38 }
39 
40 struct SystemIncludePath {
41     string value;
42     alias value this;
43 }
44 
45 /** Execute and inspect the compiler for the system includes.
46  *
47  * Note that how the compilers are inspected is hard coded.
48  */
49 SystemIncludePath[] deduceSystemIncludes(CompileCommand cmd, const Compiler compiler) {
50     return deduceSystemIncludes(cmd.command, compiler);
51 }
52 
53 /// ditto
54 SystemIncludePath[] deduceSystemIncludes(const string[] cmd, const Compiler compiler) {
55     import std.process : execute;
56 
57     if (cmd.empty || compiler.empty)
58         return null;
59 
60     if (auto v = compiler in cacheSysIncludes) {
61         return *v;
62     }
63 
64     auto args = systemCompilerArg(cmd, compiler);
65 
66     auto res = execute(args);
67     if (res.status != 0) {
68         logger.tracef("Failed to inspect the compiler for system includes: %-(%s %)", args);
69         logger.trace(res.output);
70         return null;
71     }
72 
73     auto incls = parseCompilerOutput(res.output);
74     cacheSysIncludes[compiler] = incls;
75 
76     return incls;
77 }
78 
79 private:
80 
81 string[] systemCompilerArg(const string[] cmd, const Compiler compiler) {
82     string[] args = ["-v", "/dev/null", "-fsyntax-only"];
83     if (auto v = language(compiler, cmd)) {
84         args = [v] ~ args;
85     }
86     if (auto v = sysroot(cmd)) {
87         args ~= v;
88     }
89     return [compiler.value] ~ args;
90 }
91 
92 SystemIncludePath[] parseCompilerOutput(const string output) {
93     auto lines = output.splitLines;
94     const start = lines.countUntil("#include <...> search starts here:") + 1;
95     const end = lines.countUntil("End of search list.");
96     if (start == 0 || end == 0 || start > end)
97         return null;
98 
99     return lines[start .. end].map!(a => SystemIncludePath(a.stripLeft)).array;
100 }
101 
102 SystemIncludePath[][Compiler] cacheSysIncludes;
103 
104 // assumes that compilers adher to the gcc and llvm commands use of --sysroot /
105 // -isysroot.
106 // depends on the fact that CompileCommand.Command always splits e.g. a
107 // --isysroot=foo to ["--sysroot", "foo"].
108 const(string[]) sysroot(const string[] cmd) {
109     foreach (flag; ["--sysroot", "-isysroot"]) {
110         auto index = cmd.countUntil!(a => a.startsWith(flag));
111         if (index >= 0 && (index + 2) <= cmd.length)
112             return cmd[index .. index + 2];
113     }
114 
115     return null;
116 }
117 
118 @("shall extract --sysroot and its argument")
119 unittest {
120     ["foo", "--sysroot", "bar"].sysroot.shouldEqual(["--sysroot", "bar"]);
121     ["foo", "-isysroot", "bar"].sysroot.shouldEqual(["-isysroot", "bar"]);
122 }
123 
124 // assumes that compilers adher to the gcc and llvm commands of using -xLANG
125 string language(Compiler compiler, const string[] cmd) {
126     import std.path : baseName;
127     import std.typecons : No;
128 
129     auto index = cmd.countUntil!(a => a.startsWith("-x")) + 1;
130     if (index > 0 && index < cmd.length)
131         return cmd[index];
132 
133     switch (compiler.baseName) {
134     case "cc":
135     case "clang":
136     case "gcc":
137         return "-xc";
138     case "c++":
139     case "clang++":
140     case "g++":
141         return "-xc++";
142     default:
143     }
144 
145     return null;
146 }
147 
148 @("shall parse the system flags")
149 unittest {
150     import std.typecons : Tuple;
151 
152     // arrange
153     immutable compiler_output = `Using built-in specs.
154 COLLECT_GCC=gcc
155 COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
156 OFFLOAD_TARGET_NAMES=nvptx-none
157 OFFLOAD_TARGET_DEFAULT=1
158 Target: x86_64-linux-gnu
159 Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.3.0-27ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with
160 -gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-n
161 ls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-d
162 efault-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic
163  --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
164 Thread model: posix
165 gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04)
166 COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mtune=generic' '-march=x86-64'
167  /usr/lib/gcc/x86_64-linux-gnu/7/cc1 -quiet -v -imultiarch x86_64-linux-gnu /dev/null -quiet -dumpbase null -mtune=generic -march=x86-64 -auxbase null -version -fsyntax-only -o /dev/null -fstack-protector-strong -Wformat
168  -Wformat-security
169 GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu)
170         compiled by GNU C version 7.3.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP
171 
172 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
173 ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
174 ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/7/../../../../x86_64-linux-gnu/include"
175 #include "..." search starts here:
176 #include <...> search starts here:
177  /usr/lib/gcc/x86_64-linux-gnu/7/include/foo
178  /usr/local/include
179  /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
180  /usr/include/x86_64-linux-gnu
181  /usr/include
182 End of search list.
183 GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu)
184         compiled by GNU C version 7.3.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP
185 
186 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
187 Compiler executable checksum: c8081a99abb72bbfd9129549110a350c
188 COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/
189 LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/us
190 r/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../:/lib/:/usr/lib/
191 COLLECT_GCC_OPTIONS='-v' '-fsyntax-only' '-mtune=generic' '-march=x86-64'`;
192 
193     // act
194     auto sysflags = parseCompilerOutput(compiler_output);
195 
196     // assert
197     "/usr/lib/gcc/x86_64-linux-gnu/7/include/foo".shouldBeIn(sysflags);
198     "/usr/local/include".shouldBeIn(sysflags);
199     "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed".shouldBeIn(sysflags);
200     "/usr/include/x86_64-linux-gnu".shouldBeIn(sysflags);
201     "/usr/include".shouldBeIn(sysflags);
202 }